using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenTK;
using System.IO;
using System.Collections.ObjectModel;
using System.Globalization;

namespace MKDS_Course_Editor.KCL
{
    class Obj2Kcl
    {
        public void ConvertToKcl(string infile, string outfile)
        {
            object[] o = read_obj(infile);
            write_kcl(outfile, o[0] as List<Triangle>, (Vector3)o[1], (Vector3)o[2], int.MaxValue/*10*/, 1, o[3] as string[], o[4] as int[]);
        }
        private object[] read_obj(string filename)
        {
            Vector3 bb_min = new Vector3();
            Vector3 bb_max = new Vector3();
            List<Vector3> vertices = new List<Vector3>();
            List<Triangle> triangles = new List<Triangle>();
            List<int> Materialu = new List<int>();
            List<string> names = new List<string>();
            Stream fs = File.OpenRead(filename);
            StreamReader sr = new StreamReader(fs);

            string curmaterial = "";
            CultureInfo usahax = new CultureInfo("en-US");

            string curline;
            while ((curline = sr.ReadLine()) != null)
            {
                curline = curline.Trim();

                // skip empty lines and comments
                if (curline.Length < 1) continue;
                if (curline[0] == '#') continue;

                string[] parts = curline.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                if (parts.Length < 1) continue;
                int face = 0;
                switch (parts[0])
                {
                    case "mtllib": // material lib file
                        {
                            //string filename = curline.Substring(parts[0].Length + 1).Trim();
                            //OBJ_LoadMTL(m_ModelPath + filename);
                        }
                        break;

                    case "usemtl": // material name
                        if (parts.Length < 2) continue;
                        curmaterial = parts[1];
                        if (!names.Contains(curmaterial))
                        {
                            names.Add(curmaterial);
                        }
                        break;

                    case "v": // vertex
                        {
                            if (parts.Length < 4) continue;
                            float x = float.Parse(parts[1], usahax);
                            float y = float.Parse(parts[2], usahax);
                            float z = float.Parse(parts[3], usahax);

                            if (x < bb_min.X || bb_min.X == 0) { bb_min.X = x; }
                            if (y < bb_min.Y || bb_min.Y == 0) { bb_min.Y = y; }
                            if (z < bb_min.Z || bb_min.Z == 0) { bb_min.Z = z; }
                            if (x > bb_max.X || bb_max.X == 0) { bb_max.X = x; }
                            if (y > bb_max.Y || bb_max.Y == 0) { bb_max.Y = y; }
                            if (z > bb_max.Z || bb_max.Z == 0) { bb_max.Z = z; }
                            vertices.Add(new Vector3(x, y, z));
                        }
                        break;

                    case "vt": // texcoord
                        {
                            //if (parts.Length < 2) continue;
                            //float s = float.Parse(parts[1], usahax);
                            //float t = (parts.Length < 3) ? 0f : float.Parse(parts[2], usahax);

                            //m_TexCoords.Add(new Vector2(s, t));
                        }
                        break;

                    case "vn": // normal
                        {
                            //if (parts.Length < 4) continue;
                            //float x = float.Parse(parts[1], usahax);
                            //float y = float.Parse(parts[2], usahax);
                            //float z = float.Parse(parts[3], usahax);

                            //Vector3 vec = new Vector3(x, y, z);
                            //vec.Normalize();
                            //m_Normals.Add(vec);
                        }
                        break;

                    case "f": // face
                        {
                            if (parts.Length < 4) continue;
                            Vector3 u = vertices[int.Parse(parts[1].Split('/')[0]) - 1];
                            Vector3 v = vertices[int.Parse(parts[2].Split('/')[0]) - 1];
                            Vector3 w = vertices[int.Parse(parts[3].Split('/')[0]) - 1];
                            if (norm_sq(Vector3.Cross(v - u, w - u)) < 0.001) { continue; } //#TODO: find a better solution
                            triangles.Add(new Triangle(u, v, w));
                            Materialu.Add(names.IndexOf(curmaterial));
                        }
                        break;
                }
            }

            sr.Close();
            return new object[] { triangles, bb_min, bb_max, names.ToArray(), Materialu.ToArray() };
        }

        private void write_kcl(string filename, List<Triangle> triangles, Vector3 bb_min, Vector3 bb_max, int max_triangles, int min_width, string[] names, int[] materialu)
        {
            bb_min -= new Vector3(1000f, 1000f, 1000f);
            bb_max += new Vector3(1000f, 1000f, 1000f);
            kclType ty = new kclType(names);
            ty.DialogResult = System.Windows.Forms.DialogResult.None;
            ty.ShowDialog();
            while (ty.DialogResult != System.Windows.Forms.DialogResult.OK) ;
            Dictionary<string, ushort> Mapping;
            Dictionary<string, bool> Colli;
            Mapping = ty.Mapping;
            Colli = ty.Colli;

            int n_min = next_exponent(min_width, 2);
            int n_x = Math.Max(next_exponent(bb_max.X - bb_min.X, 2), n_min);
            int n_y = Math.Max(next_exponent(bb_max.Y - bb_min.Y, 2), n_min);
            int n_z = Math.Max(next_exponent(bb_max.Z - bb_min.Z, 2), n_min);
            int n = Math.Max((int)min(n_x, n_y, n_z), n_min);

            float divs_x = (float)Math.Pow(2, (n_x - n));
            float divs_y = (float)Math.Pow(2, (n_y - n));
            float divs_z = (float)Math.Pow(2, (n_z - n));
            float width = (float)Math.Pow(2, n);

            List<OctreeNode> octree = new List<OctreeNode>();
            List<int> indices = new List<int>();
            int l = 0;
            foreach (Triangle t in triangles)
            {
                if (Colli[names[materialu[l]]])
                {
                    indices.Add(l);
                }
                l++;
            }
            for (int k = 0; k < divs_z; k++)
            {
                for (int j = 0; j < divs_y; j++)
                {
                    for (int i = 0; i < divs_x; i++)
                    {
                        octree.Add(new OctreeNode(bb_min + width * new Vector3(i, j, k), width, triangles, indices, max_triangles, min_width));
                    }
                }
            }

            //OctreeNode octree = [OctreeNode(bb_min + width*Vector(i,j,k),width,triangles,xrange(len(triangles)),max_triangles,min_width)
            //          for k in xrange(divs_z) for j in xrange(divs_y) for i in xrange(divs_x)]

            EndianBinaryWriter f = new EndianBinaryWriter(File.Create(filename), Endianness.LittleEndian);

            //# header
            f.Write((UInt32)0); //# vertex offset placeholder
            f.Write((UInt32)0); //# vector offset placeholder
            f.Write((UInt32)0); //# triangle offset placeholder
            f.Write((UInt32)0); //# octree offset placeholder
            f.Write((UInt32)122880); //# unknown
            f.Write((Int32)(bb_min.X * 4096)); //# x min
            f.Write((Int32)(bb_min.Y * 4096)); //# y min
            f.Write((Int32)(bb_min.Z * 4096)); //# z min
            f.Write((UInt32)((0xFFFFFFFF << n_x) & 0xFFFFFFFF)); //# x mask
            f.Write((UInt32)((0xFFFFFFFF << n_y) & 0xFFFFFFFF)); //# y mask
            f.Write((UInt32)((0xFFFFFFFF << n_z) & 0xFFFFFFFF)); //# z mask
            f.Write((UInt32)n); //# coordinate shift
            f.Write((UInt32)(n_x - n)); //# y shift
            f.Write((UInt32)(n_x - n + n_y - n)); //# z shift
            f.Write((UInt32)102400); //# unknown

            //# vertex section
            int pos = (int)f.BaseStream.Position;
            f.BaseStream.Position = 0;
            f.Write((Int32)pos); //# vertex offset
            f.BaseStream.Position = pos;
            List<Vector3> Vt = new List<Vector3>();
            List<int> Videx = new List<int>();
            int ii = 0;
            foreach (Triangle t in triangles)
            {
                if (Colli[names[materialu[ii]]])
                {
                    int d = containsVector3(t.u, Vt);
                    if (d == -1)
                    {
                        Vt.Add(t.u);
                        Videx.Add(Vt.Count - 1);
                    }
                    else
                    {
                        Videx.Add(d);
                    }
                }
                ii++;
            }
            foreach (Vector3 c in Vt)
            {
                f.Write((Int32)(c.X * 4096));
                f.Write((Int32)(c.Y * 4096));
                f.Write((Int32)(c.Z * 4096));
            }

            //# vector section
            pos = (int)f.BaseStream.Position;
            f.BaseStream.Position = 4;
            f.Write((Int32)pos); //# vector offset
            f.BaseStream.Position = pos;

            List<Vector3> Norm = new List<Vector3>();
            List<int> Nidex = new List<int>();
            ii = 0;
            foreach (Triangle t in triangles)
            {
                if (Colli[names[materialu[ii]]])
                {
                    Vector3 a = -unit(Vector3.Cross(t.w - t.u, t.n));
                    Vector3 b = unit(Vector3.Cross(t.v - t.u, t.n));
                    Vector3 c = unit(Vector3.Cross(t.w - t.v, t.n));
                    int d = containsVector3(t.n, Norm);
                    if (d == -1)
                    {
                        Norm.Add(t.n);
                        Nidex.Add(Norm.Count - 1);
                    }
                    else
                    {
                        Nidex.Add(d);
                    }
                    d = containsVector3(a, Norm);
                    if (d == -1)
                    {
                        Norm.Add(a);
                        Nidex.Add(Norm.Count - 1);
                    }
                    else
                    {
                        Nidex.Add(d);
                    }
                    d = containsVector3(b, Norm);
                    if (d == -1)
                    {
                        Norm.Add(b);
                        Nidex.Add(Norm.Count - 1);
                    }
                    else
                    {
                        Nidex.Add(d);
                    }
                    d = containsVector3(c, Norm);
                    if (d == -1)
                    {
                        Norm.Add(c);
                        Nidex.Add(Norm.Count - 1);
                    }
                    else
                    {
                        Nidex.Add(d);
                    }
                }
                ii++;
            }

            foreach (Vector3 c in Norm)
            {
                f.Write((Int16)(c.X * 4096));
                f.Write((Int16)(c.Y * 4096));
                f.Write((Int16)(c.Z * 4096));
            }

            while (f.BaseStream.Position % 4 != 0) { f.Write((Byte)0); }

            //# triangle section
            pos = (int)f.BaseStream.Position;
            f.BaseStream.Position = 8;
            f.Write((Int32)(pos - 16)); //# triangle offset
            f.BaseStream.Position = pos;
            int m = 0;
            ii = 0;
            foreach (Triangle t in triangles)
            {
                if (Colli[names[materialu[ii]]])
                {
                    Vector3 c = unit(Vector3.Cross(t.w - t.v, t.n));
                    f.Write((Int32)(Vector3.Dot(t.w - t.u, c) * 4096)); //# length
                    f.Write((UInt16)(Videx[m])); //# vertex index
                    f.Write((UInt16)(Nidex[4 * m])); //# normal index
                    f.Write((UInt16)(Nidex[4 * m + 1])); //# a index
                    f.Write((UInt16)(Nidex[4 * m + 2])); //# b index
                    f.Write((UInt16)(Nidex[4 * m + 3])); //# c index
                    f.Write(stringtobyte(string.Format("{0:X4}", Mapping[names[materialu[ii]]])),0,2); //# surface type
                    m++;
                }
                ii++;
            }

            //# octree section
            pos = (int)f.BaseStream.Position;
            f.BaseStream.Position = 12;
            f.Write((Int32)pos); //# octree offset
            f.BaseStream.Position = pos;
            long bas = f.BaseStream.Position;
            Queue<OctreeNode> nodes;
            Queue<long> nodeStarts;
            OctreeNode current;
            Collection<ushort> dataSection;
            int progress, totalNodes;
            long freeAddress, startAddress, dataStart;

            startAddress = freeAddress = f.BaseStream.Position;
            nodes = new Queue<OctreeNode>();
            dataSection = new Collection<ushort>();
            nodeStarts = new Queue<long>();

            for (int i = 0; i < octree.Count; i++)
            {
                nodeStarts.Enqueue(freeAddress);
                nodes.Enqueue(octree[i]);
            }
            freeAddress += octree.Count * 4;
            progress = 0;
            totalNodes = octree.Count;
            while (nodes.Count > 0)
            {
                current = nodes.Dequeue();

                if (current.is_leaf)
                {
                    f.Write(0);
                    nodeStarts.Dequeue();
                }
                else
                {
                    f.Write((int)(freeAddress - nodeStarts.Dequeue()));
                    for (int i = 0; i < current.branches.Count; i++)
                    {
                        nodes.Enqueue(current.branches[i]);
                        nodeStarts.Enqueue(freeAddress);
                        totalNodes++;
                    }
                    freeAddress += 0x20;
                }

                progress++;
                //progressHandler.Invoke(10 + ((45 * progress) / totalNodes), Program.GetString("ProgressWritingOctree"));
            }

            dataStart = f.BaseStream.Position;
            freeAddress = startAddress;
            f.BaseStream.Seek(startAddress, SeekOrigin.Begin);
            for (int i = 0; i < octree.Count; i++)
            {
                nodeStarts.Enqueue(freeAddress);
                nodes.Enqueue(octree[i]);
            }
            freeAddress += octree.Count * 4;
            progress = 0;

            while (nodes.Count > 0)
            {
                startAddress = f.BaseStream.Position;
                current = nodes.Dequeue();

                if (current.is_leaf)
                {
                    f.Write((int)(int.MinValue | (f.BaseStream.Length - nodeStarts.Dequeue() - 2)));
                    f.BaseStream.Seek(0, SeekOrigin.End);
                    for (int i = 0; i < current.indices.Count; i++)
                        f.Write((ushort)(current.indices[i] + 1));
                    f.Write((ushort)0);

                    for (int i = 0; i < current.indices.Count; i++)
                        dataSection.Add((ushort)(current.indices[i] + 1));
                    dataSection.Add(0);

                    f.BaseStream.Seek(startAddress + 4, SeekOrigin.Begin);
                }
                else
                {
                    nodeStarts.Dequeue();
                    f.BaseStream.Seek(4, SeekOrigin.Current);
                    for (int i = 0; i < current.branches.Count; i++)
                    {
                        nodes.Enqueue(current.branches[i]);
                        nodeStarts.Enqueue(freeAddress);
                    }
                    freeAddress += 0x20;
                }

                progress++;
                //progressHandler.Invoke(55 + ((45 * progress) / totalNodes), Program.GetString("ProgressWritingOctree"));
            }
            f.Close();
        }
        private static byte[] stringtobyte(string a)
        {
            byte[] buffer;
            int discarded;
            buffer = Utility.HexEncoding.GetBytes(a, out discarded);
            return buffer;
        }
        private static int containsVector3(Vector3 a, List<Vector3> b)
        {
            for (int i = 0; i < b.Count; i++)
            {
                if (b[i].X == a.X && b[i].Y == a.Y && b[i].Z == a.Z)
                {
                    return i;
                }
            }
            return -1;
        }
        private static int next_exponent(float value, int bas)
        {
            if (value <= 1) { return 0; }
            return (int)(Math.Ceiling(Math.Log(value, bas)));
        }
        private static float norm_sq(Vector3 a)
        {
            return a.X * a.X + a.Y * a.Y + a.Z * a.Z;
        }
        private static float norm(Vector3 a)
        {
            return (float)Math.Sqrt(norm_sq(a));
        }
        private static Vector3 unit(Vector3 a)
        {
            Vector3 r = a;
            r = Vector3.Divide(r,norm(r));
            return r;
        }
        private class Triangle
        {
            public Vector3 u;
            public Vector3 v;
            public Vector3 w;
            public Vector3 n;
            public Triangle(Vector3 u, Vector3 v, Vector3 w)
            {
                this.u = u;
                this.v = v;
                this.w = w;
                this.n = unit(Vector3.Cross(v - u, w - u));
            }
        }
        private class OctreeNode : Cube
        {
            public bool is_leaf;
            public List<int> indices = new List<int>();
            public List<OctreeNode> branches = new List<OctreeNode>();
            public OctreeNode(Vector3 bas, float width, List<Triangle> triangles, List<int> indices, int max_triangles, int min_width)
            {
                //Cube.__init__(self,base,width)
                this.hw = width / 2.0f;
                this.c = bas + new Vector3(this.hw, this.hw, this.hw);
                this.is_leaf = true;
                bool stay = false;
                foreach (int i in indices)
                {
                    bool stay2;
                    if (tricube_overlap(triangles[i], this,out stay2)) { this.indices.Add(i); }
                    if (stay == false && stay2)
                    {
                        stay = stay2;
                    }
                }
                if (this.indices.Count > max_triangles && this.hw >= min_width && !stay)
                {
                    this.is_leaf = false;
                    for (int k = 0; k < 2; k++)
                    {
                        for (int j = 0; j < 2; j++)
                        {
                            for (int i = 0; i < 2; i++)
                            {
                                this.branches.Add(new OctreeNode(bas + this.hw * new Vector3(i, j, k), this.hw, triangles, this.indices, max_triangles, min_width));
                            }
                        }
                    }
                    this.indices.Clear();
                }
            }
        }
        private class Cube
        {
            public float hw;
            public Vector3 c;
            //def __init__(self,base,width):
            //  self.hw = width/2.0
            //  self.c = base + Vector(self.hw,self.hw,self.hw)

            public bool axis_test(float a1, float a2, float b1, float b2, float c1, float c2, Cube c)
            {
                float p = a1 * b1 + a2 * b2;
                float q = a1 * c1 + a2 * c2;
                float r = c.hw * (Math.Abs(a1) + Math.Abs(a2));
                return Math.Min(p, q) > r || Math.Max(p, q) < -r;
            }

            public bool tricube_overlap(Triangle t, Cube c, out bool stay)
            {
                //c.hw += 1;
                //c.hw += 10;
                stay = false;

                Vector3 v0 = t.u - c.c;
                Vector3 v1 = t.v - c.c;
                Vector3 v2 = t.w - c.c;

                if (min(v0.X, v1.X, v2.X) > c.hw || max(v0.X, v1.X, v2.X) < -c.hw) { return false; }
                if (min(v0.Y, v1.Y, v2.Y) > c.hw || max(v0.Y, v1.Y, v2.Y) < -c.hw) { return false; }
                if (min(v0.Z, v1.Z, v2.Z) > c.hw || max(v0.Z, v1.Z, v2.Z) < -c.hw) { return false; }

                float d = Vector3.Dot(t.n, v0);
                float r = c.hw * (Math.Abs(t.n.X) + Math.Abs(t.n.Y) + Math.Abs(t.n.Z));
                if (d > r || d < -r) { return false; }

                Vector3 e = v1 - v0;
                if (axis_test(e.Z, -e.Y, v0.Y, v0.Z, v2.Y, v2.Z, c)) { return false; }
                if (axis_test(-e.Z, e.X, v0.X, v0.Z, v2.X, v2.Z, c)) { return false; }
                if (axis_test(e.Y, -e.X, v1.X, v1.Y, v2.X, v2.Y, c)) { return false; }

                e = v2 - v1;
                if (axis_test(e.Z, -e.Y, v0.Y, v0.Z, v2.Y, v2.Z, c)) { return false; }
                if (axis_test(-e.Z, e.X, v0.X, v0.Z, v2.X, v2.Z, c)) { return false; }
                if (axis_test(e.Y, -e.X, v0.X, v0.Y, v1.X, v1.Y, c)) { return false; }

                e = v0 - v2;
                if (axis_test(e.Z, -e.Y, v0.Y, v0.Z, v1.Y, v1.Z, c)) { return false; }
                if (axis_test(-e.Z, e.X, v0.X, v0.Z, v1.X, v1.Z, c)) { return false; }
                if (axis_test(e.Y, -e.X, v1.X, v1.Y, v2.X, v2.Y, c)) { return false; }

                if (v0.Y > (c.hw / 2.0f) || v1.Y > (c.hw / 2.0f) || v2.Y > (c.hw / 2.0f))// || v0.Y < -(c.hw / 2.0f) || v1.Y < -(c.hw / 2.0f) || v2.Y < -(c.hw / 2.0f))
                {
                    stay = true;
                }
                return true;

            }
        }
        private static float min(params float[] v)
        {
            float min = float.MaxValue;
            foreach (float f in v)
            {
                if (f < min)
                {
                    min = f;
                }
            }
            return min;
        }
        private static float max(params float[] v)
        {
            float max = float.MinValue;
            foreach (float f in v)
            {
                if (f > max)
                {
                    max = f;
                }
            }
            return max;
        }
        private static Vector3 MinMax(Vector3 min, Vector3 max, Vector3 input)
        {
            Vector3 a = input;
            if (min.X > a.X)
                min.X = a.X;
            if (min.Y > a.Y)
                min.Y = a.Y;
            if (min.Z > a.Z)
                min.Z = a.Z;
            if (max.X < a.X)
                max.X = a.X;
            if (max.Y < a.Y)
                max.Y = a.Y;
            if (max.Z < a.Z)
                max.Z = a.Z;
            return a;
        }
    }
}
